"""
Django-python-libmemcached (a django wrapper for python-libmemcached)

This package defines set of memcached backends to a simple API.

Client code should not access a memcached backend directly; instead it should
either use the "memcached_client" variable made available here, or it should use the
get_memcached() function made available here. get_memcached() takes a backend URI
(e.g. "memcached://127.0.0.1:11211/") and returns an instance of a backend
memcached class.

To use Memcached with Django, set ``MEMCACHED_BACKEND`` to
``memcached://ip:port/``, where ``ip`` is the IP address of the Memcached
daemon and ``port`` is the port on which Memcached is running.

In this example, Memcached is running on localhost (127.0.0.1) port 11211::

    MEMCACHED_BACKEND = 'memcached://127.0.0.1:11211/'

One excellent feature of Memcached is its ability to share cache over multiple
servers. This means you can run Memcached daemons on multiple machines, and the
program will treat the group of machines as a *single* cache, without the need
to duplicate cache values on each machine. To take advantage of this feature,
include all server addresses in ``MEMCACHED_BACKEND``, separated by semicolons.

In this example, the cache is shared over Memcached instances running on IP
address 172.19.26.240 and 172.19.26.242, both on port 11211::

    MEMCACHED_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'

In the following example, the cache is shared over Memcached instances running
on the IP addresses 172.19.26.240 (port 11211), 172.19.26.242 (port 11212), and
172.19.26.244 (port 11213)::

    MEMCACHED_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'

A final point about Memcached is that memory-based caching has one
disadvantage: Because the cached data is stored in memory, the data will be
lost if your server crashes. Clearly, memory isn't intended for permanent data
storage, so don't rely on memory-based caching as your only data storage.
Without a doubt, *none* of the Django caching backends should be used for
permanent storage -- they're all intended to be solutions for caching, not
storage -- but we point this out here because memory-based caching is
particularly temporary.

Dependence
python-libmemcached     http://code.google.com/p/python-libmemcached/
Thanks Douban.com

See http://code.google.com/p/django-python-libmemcached/ for source code on the Google Project Hosting.
Author  hanshaojie@gmail.com

Use
from your_project.libmemcached import memcached_client as mc
"""

import time

from cgi import parse_qsl
from django.conf import settings
from django.core.cache.backends.base import InvalidCacheBackendError
from django.utils.encoding import smart_unicode, smart_str

try:
    import cmemcached as memcache
    import warnings
    warnings.warn(
        "Support for the 'cmemcached' library has been deprecated. Please use python-libmemcached instead.",
        PendingDeprecationWarning
    )
except ImportError:
    try:
        import memcache
    except:
        raise InvalidCacheBackendError("Memcached cache backend requires either the 'memcache' or 'cmemcached' library")

class PythonLibmemcached():
    def __init__(self, server, params):
        """
        Set default timeout
        Connect Memcached Server.
        """
        timeout = params.get('timeout', 300)
        try:
            timeout = int(timeout)
        except (ValueError, TypeError):
            timeout = 300
        self.default_timeout = timeout
        
        self._cache = memcache.Client(server.split(';'))

    def _get_memcache_timeout(self, timeout):
        """
        Memcached deals with long (> 30 days) timeouts in a special
        way. Call this function to obtain a safe value for your timeout.
        """
        timeout = timeout or self.default_timeout
        if timeout > 2592000: # 60*60*24*30, 30 days
            # See http://code.google.com/p/memcached/wiki/FAQ
            # "You can set expire times up to 30 days in the future. After that
            # memcached interprets it as a date, and will expire the item after
            # said date. This is a simple (but obscure) mechanic."
            #
            # This means that we have to switch to absolute timestamps.
            timeout += int(time.time())
        return timeout

    def add(self, key, value, timeout=0):
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        return self._cache.add(smart_str(key), value, self._get_memcache_timeout(timeout))

    def get(self, key, default=None):
        val = self._cache.get(smart_str(key))
        if val is None:
            return default
        return val

    def set(self, key, value, timeout=0):
        self._cache.set(smart_str(key), value, self._get_memcache_timeout(timeout))

    def delete(self, key):
        self._cache.delete(smart_str(key))

    def get_many(self, keys):
        return self._cache.get_multi(map(smart_str,keys))
    
    def incr(self, key, delta=1):
        val = self._cache.incr(key, delta)
        if val is None:
            raise ValueError("Key '%s' not found" % key)
        return val

    def decr(self, key, delta=1):
        val = self._cache.decr(key, delta)
        if val is None:
            raise ValueError("Key '%s' not found" % key)
        return val

    def set_many(self, data, timeout=0):
        safe_data = {}
        for key, value in data.items():
            if isinstance(value, unicode):
                value = value.encode('utf-8')
            safe_data[smart_str(key)] = value
        self._cache.set_multi(safe_data, self._get_memcache_timeout(timeout))

    def delete_many(self, keys):
        self._cache.delete_multi(map(smart_str, keys))

    def get_host_by_key(self, key):
        return self._cache.get_host_by_key(smart_str(key))


# Name for use in settings file --> name of module in "backends" directory.
# Any backend scheme that is not in this dictionary is treated as a Python
# import path to a custom backend.

def parse_backend_uri(backend_uri):
    """
    Converts the "backend_uri" into a cache scheme ('libmemcached', etc), a
    host and any extra params that are required for the backend. Returns a
    (scheme, host, params) tuple.
    """
    if backend_uri.find(':') == -1:
        raise InvalidCacheBackendError("Backend URI must start with scheme://")
    scheme, rest = backend_uri.split(':', 1)
    if not rest.startswith('//'):
        raise InvalidCacheBackendError("Backend URI must start with scheme://")

    host = rest[2:]
    qpos = rest.find('?')
    if qpos != -1:
        params = dict(parse_qsl(rest[qpos+1:]))
        host = rest[2:qpos]
    else:
        params = {}
    if host.endswith('/'):
        host = host[:-1]

    return scheme, host, params

def get_memcached(backend_uri):
    scheme, host, params = parse_backend_uri(backend_uri)
    if scheme == 'memcached':
        return PythonLibmemcached(host,params)
    raise InvalidCacheBackendError("Backend URI scheme is not libmemcached.")

memcached_client = get_memcached(settings.MEMCACHED_BACKEND)
